#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include <string.h>

#include <sys/socket.h>   // For Socket Functions
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#include "util.h"
#include "server_utils.h"
#include "bid_service.h"
#include "dynamic_session.h"
#include "sock.h"

#define BID_SERVICE_GREETING "Hello, you are now connected to bid server\r\n"
#define BSD_AUTH_SUCCESS_MSG "200 Ok Welcome\r\n"
#define BSD_BID_SUCCESS_MSG "200 Ok bid submitted successfully\r\n"
#define BSD_BYE_MSG "200 Bye\r\n"
#define BSD_AUTH_FAILURE_ERRMSG "401 Sorry, username or password mismatch\r\n"
#define BSD_INVALID_SYNTAX_ERRMSG "400 Invalid Syntax\r\n"
#define BSD_INVALID_CMD_ERRMSG "400 Invalid Command\r\n"
#define BSD_INVALID_STATE_ERRMSG "405 Invalid state command\r\n"
#define BSD_SERVER_INTERNAL_ERRMSG "500 Server Internal Error\r\n"

#define WSPACE " "

/* Globals */
/* Entry-point into bid service..
 * accept connection and depending on server setting
 * either use dynamic session or pooled session to
 * handle the request.
 */
void handle_bid_service_connect(int fd) {
	/*
	 * 1. Accept the conection
	 * 2. Queue the connected socket to request queue
	 * 3. Alert the service threads.
	 */
	int cSocket = -1;
	socklen_t len = 0;
	struct sockaddr_in cAddress;
	char client_ip[INET_ADDRSTRLEN + 1];

	LOG(DEBUG, "Inside handle_connect_request");
	memset((void *) &cAddress, 0, sizeof(struct sockaddr_in));

	cSocket = accept(fd, (struct sockaddr*) &cAddress, &len);
	if (cSocket == -1) {
		LOG(ERROR, "Failed in accept with error: %d", errno);
		return;
	}

	LOG(DEBUG, "Connection from %s, port %d\n", inet_ntop(AF_INET,
			&cAddress.sin_addr, client_ip, sizeof(client_ip)), ntohs(
			cAddress.sin_port));

	LOG(DEBUG, "Assigned socket %d for the client", cSocket);

	// depending on mode choose action
	if (g.mode == MODE_DYNAMIC_THREADS) {
		start_dynamic_bid_session(cSocket, cAddress);
	} else {
		//Add the connection to the accept queue
		//condition broadcast.
	}

}

/**
 * Initialise thread context parameter to appropriate initial values.
 */
void initialize_context(session_ctx* ctx) {
	memset(ctx, 0, sizeof(session_ctx));

	ctx->eye_catcher = BSD_SESSION_CTX;
	ctx->sock_fd = INVALID_FD;

	ctx->curr_state = BSE_ST_CONNECTED;
	ctx->event = BSE_EV_SUCCESS;

	return;
}

/******************************************************************************************
 *  Finite State Machine for the bid service
 ******************************************************************************************/

typedef struct transition_table_def {
	bid_fsm_transition_key transition_key;
	bid_service_state next_state;
	bid_service_action *action;
} transition;

static const transition transition_table[] = {
		{ { BSE_ST_CONNECTED, BSE_EV_SUCCESS }, BSE_ST_VALIDATING_CONNECTION,
				do_validate_connection },

		{ { BSE_ST_VALIDATING_CONNECTION, BSE_EV_ACCEPT },
				BSE_ST_SENDING_GREETING, do_send_greeting }, { {
				BSE_ST_VALIDATING_CONNECTION, BSE_EV_REJECT },
				BSE_ST_CLOSING_CONNECTION, do_close_connection },

		{ { BSE_ST_SENDING_GREETING, BSE_EV_SUCCESS }, BSE_ST_RECEIVING_CMD,
				do_receive_command }, { { BSE_ST_SENDING_GREETING,
				BSE_EV_CONN_ERROR }, BSE_ST_CLOSING_CONNECTION,
				do_close_connection },

		{ { BSE_ST_RECEIVING_CMD, BSE_EV_SUCCESS }, BSE_ST_VALIDATING_CMD,
				do_validate_command }, { { BSE_ST_RECEIVING_CMD,
				BSE_EV_CONN_ERROR }, BSE_ST_CLOSING_CONNECTION,
				do_close_connection },

		{ { BSE_ST_VALIDATING_CMD, BSE_EV_SUCCESS }, BSE_ST_EXECUTING_CMD,
				do_execute_command },
		{ { BSE_ST_VALIDATING_CMD, BSE_EV_INVALID_CMD },
				BSE_ST_REPORTING_ERROR, do_report_error }, { {
				BSE_ST_VALIDATING_CMD, BSE_EV_INVALID_SYNTAX },
				BSE_ST_REPORTING_ERROR, do_report_error }, { {
				BSE_ST_VALIDATING_CMD, BSE_EV_INVALID_STATE },
				BSE_ST_REPORTING_ERROR, do_report_error },

		{ { BSE_ST_REPORTING_ERROR, BSE_EV_SUCCESS },
				BSE_ST_CLOSING_CONNECTION, do_close_connection }, { {
				BSE_ST_REPORTING_ERROR, BSE_EV_CONN_ERROR },
				BSE_ST_CLOSING_CONNECTION, do_close_connection },

		{ { BSE_ST_EXECUTING_CMD, BSE_EV_SUCCESS }, BSE_ST_RECEIVING_CMD,
				do_receive_command }, { { BSE_ST_EXECUTING_CMD,
				BSE_EV_CONN_ERROR }, BSE_ST_CLOSING_CONNECTION,
				do_close_connection },
		{ { BSE_ST_EXECUTING_CMD, BSE_EV_REPORT_ERROR },
				BSE_ST_REPORTING_ERROR, do_report_error },

		{ { BSE_ST_CLOSING_CONNECTION, BSE_EV_SUCCESS }, BSE_ST_END_SESSION,
				NULL },

};

static void *
binary_search(void *pKey, char *pTable, long count, long itemSize, long keySize) {
	long low, high, mid;
	char *pEntry;
	int diff;

	low = 0;
	high = count - 1;
	while (low <= high) {
		mid = (low + high) / 2;
		pEntry = pTable + mid * itemSize;
		diff = memcmp(pKey, pEntry, keySize);
		if (diff < 0) {
			high = mid - 1;
		} else if (diff > 0) {
			low = mid + 1;
		} else {
			return pEntry; /* found */
		}
	}
	return NULL;
}

/* based on current state, and event
 * find next state and action from transition table. */

bid_service_action*
fsm_transition(session_ctx *ctx) {
	bid_fsm_transition_key transition_key;
	transition *current_transition;
	transition default_transition = { { BSE_ST_VALIDATING_CMD,
			BSE_EV_INVALID_CMD }, BSE_ST_REPORTING_ERROR, do_report_error };

	if (ctx->event == BSE_EV_END) {
		return NULL;
	}

	ctx->prev_state = ctx->curr_state; //for debugging purposes.
	memcpy(&transition_key.current_state, &ctx->curr_state,
			sizeof(bid_service_state));
	memcpy(&transition_key.event, &ctx->event, sizeof(bid_service_event));

	current_transition = binary_search(&transition_key,
			(char *) &transition_table, sizeof(transition_table)
					/ sizeof(transition), sizeof(transition),
			sizeof(bid_fsm_transition_key));

	if (current_transition == NULL) { /*
	 State / Event combination not found Case for abort,
	 nothing can be done by the calling process. Atul */
		LOG(CRITICAL, "transition table out of bounds \n"
			"prev_state = %d \n curr_state = %d, event = %d", ctx->prev_state,
				ctx->curr_state, ctx->event);
		//shutdown_server(ctx->event, "Transition table out of bounds");
		ctx->error_msg[0] = 0;
		strcpy(ctx->error_msg,
				"550 Error: Server internal error... please retry");
		current_transition = &default_transition;
	}

	ctx->prev_state = ctx->curr_state;//for debugging purposes.
	ctx->curr_state = current_transition->next_state;
	ctx->event = BSE_EV_END;
	return current_transition->action;
}

void fsm(session_ctx *ctx) {

	bid_service_action *action;

	while ((action = fsm_transition(ctx)) != NULL) {
		ctx->event = action(ctx);
	}

}

/* Command table lookup routines */
bid_cmd bid_cmd_table[] = {
		{ "auth", auth_handler, 2, UNAUTH}, { "auth-md5",
		auth_md5_handler, 0, UNAUTH }, { "auth-md5-res", auth_md5_res_handler, 1, UNAUTH}, {
		"list-items", list_items_handler, 0, UNAUTH|AUTH }, { "bid", bid_handler, 2, AUTH }, {
		"list-my-bids", list_my_bids_handler, 0, AUTH }, { "list-my-items",
		list_my_items_handler, 0, AUTH }, { NULL, NULL, 0, AUTH|UNAUTH } };

bid_cmd*
lookup_command(char* cmd) {
	int index = 0;
	while (bid_cmd_table[index].command) {
		if (!strcasecmp(cmd, bid_cmd_table[index].command)) {
			break;
		}
		index++;
	}

	if (bid_cmd_table[index].command) {
		return &bid_cmd_table[index];
	}

	return NULL;
}

/* Action Routines */
bid_service_event do_validate_connection(session_ctx *ctx) {
	LOG(DEBUG, "Inside validate connection");
	//Perform reverse lookup on IP and domain.
	LOG(DEBUG, "Reverse lookup successful");
	//Perform blacklist, whitelist checks.
	LOG(DEBUG, "Blacklist, whiltelist checkcs successful");
	return BSE_EV_ACCEPT;
}

bid_service_event do_send_greeting(session_ctx *ctx) {
	LOG(DEBUG, "Sending Greeting");
	if (!write_buffer(ctx->sock_fd, BID_SERVICE_GREETING, strlen(
			BID_SERVICE_GREETING))) {
		LOG(ERROR, "Error while sending greeting");
		return BSE_EV_CONN_ERROR;
	}
	return BSE_EV_SUCCESS;
}

bid_service_event do_receive_command(session_ctx *ctx) {
	LOG(DEBUG, "Receiving command");
	if (read_line(ctx->sock_fd, ctx->cmd_buff, BSD_CMD_BUF_MAX_SIZE) < 0) {
		LOG(ERROR, "Error while reading command");
		return BSE_EV_CONN_ERROR;
	}
	return BSE_EV_SUCCESS;
}

void reset_cmd_ctx(session_ctx *ctx) {
	int i = 0;

	free(ctx->cmd);
	for (i = 0; i < ctx->arg_count; i++) {
		free(ctx->arg[i]);
		ctx->arg[i] = NULL;
	}
	ctx->arg_count = 0;
}

bid_service_event do_validate_command(session_ctx *ctx) {
	LOG(DEBUG, "Validating command");
	char *token;
	bid_cmd *cmd;

	//Parse command and its arguments.
	token = strtok(ctx->cmd_buff, WSPACE);
	if (token) {
		ctx->cmd = strdup(token);
	} else {
		return BSE_EV_INVALID_SYNTAX;
	}

	ctx->arg_count = 0;

	token = strtok(NULL, WSPACE);
	while (token) {
		ctx->arg[ctx->arg_count++] = strdup(token);
		token = strtok(NULL, WSPACE);
	}

	//Lookup command and number of arguments for command
	cmd = lookup_command(ctx->cmd);
	if (NULL == cmd) {
		reset_cmd_ctx(ctx);
		strcpy(ctx->error_msg, BSD_INVALID_CMD_ERRMSG);
		return BSE_EV_INVALID_CMD;
	}

	if(!(ctx->session_status & cmd->state)){
		reset_cmd_ctx(ctx);
		strcpy(ctx->error_msg, BSD_INVALID_STATE_ERRMSG);
		return BSE_EV_INVALID_STATE;
	}

	if (ctx->arg_count != cmd->arg_cnt){
		reset_cmd_ctx(ctx);
		strcpy(ctx->error_msg, BSD_INVALID_SYNTAX_ERRMSG);
		return BSE_EV_INVALID_SYNTAX;
	}

	ctx->cmd_handler = (void *)cmd->handler;

	return BSE_EV_SUCCESS;
}

bid_service_event do_report_error(session_ctx *ctx) {
	LOG(DEBUG, "Reporting error");
	char errmsg[BSD_ERR_MSG_MAX_SIZE + 1];
	sprintf(errmsg, "%s\r\n", ctx->error_msg);
	if (write_buffer(ctx->sock_fd, errmsg, strlen(errmsg)) < 0) {
		LOG(ERROR, "Error while sending greeting");
		return BSE_EV_CONN_ERROR;
	}
	return BSE_EV_SUCCESS;
}

bid_service_event do_execute_command(session_ctx *ctx) {
	LOG(DEBUG, "Executing command");
	bid_service_event event;
	if (! ctx->cmd_handler){
		strcpy(ctx->error_msg, BSD_SERVER_INTERNAL_ERRMSG);
		return BSE_EV_REPORT_ERROR;
	}

	event = ((bid_cmd_handler *)ctx->cmd_handler)(ctx);
	return event;
}

bid_service_event do_close_connection(session_ctx *ctx) {
	LOG(DEBUG, "Closing connection");
	close(ctx->sock_fd);
	ctx->sock_fd = INVALID_FD;
	return BSE_EV_SUCCESS;
}

bid_service_event
auth_handler(session_ctx *ctx){
	LOG(DEBUG, "Inside auth handler");
	return BSE_EV_SUCCESS;
}

bid_service_event
auth_md5_handler(session_ctx *ctx){
	LOG(DEBUG, "Inside auth-md5 handler");
	return BSE_EV_SUCCESS;
}

bid_service_event
auth_md5_res_handler(session_ctx *ctx){
	LOG(DEBUG, "Inside auth-md5-res handler");
	return BSE_EV_SUCCESS;
}

bid_service_event
list_items_handler(session_ctx *ctx){
	LOG(DEBUG, "Inside list items handler");
	return BSE_EV_SUCCESS;
}

bid_service_event
bid_handler(session_ctx *ctx){
	LOG(DEBUG, "Inside bid handler");
	return BSE_EV_SUCCESS;
}

bid_service_event
list_my_bids_handler(session_ctx *ctx){
	LOG(DEBUG, "Inside list-my-bids handler");
	return BSE_EV_SUCCESS;
}

bid_service_event
list_my_items_handler(session_ctx *ctx){
	LOG(DEBUG, "Inside list-my-items handler");
	return BSE_EV_SUCCESS;
}

/*        int count_read = 0;

 memset(bucket,'\0',MAX_BUCKETSIZE+1);

 //read socket
 if ((read_cnt = read_line(ctx->sock_fd, bucket, MAX_BUCKETSIZE)) <= 0){
 LOG(DEBUG, "Error while reading socket");
 if (read_cnt == 0)
 {
 LOG(DEBUG, "Connection closed by client");
 }
 break;
 }

 LOG(DEBUG, "Read: %d, %s", read_cnt, bucket);

 //echo response
 if (write_buffer(ctx->sock_fd, bucket, read_cnt) != read_cnt){
 LOG(DEBUG, "Failed to write to the socket, probable error %d", errno);
 return NULL;
 }
 LOG(DEBUG, "Wrote buffer size: %d ", count_read);
 */

/*
 Atul ToDo
 1. Read line - using buffer rather than single char. i.e. buffering
 2. Timeout - timer implementation.
 3. Graceful shutdown - Join the threads - i.e. keep track of active sessions in linked list.
 4. Protocol state machine.
 */

/* Protocol State machine
 1. Create table with command vs handler.
 2. Loop on read and write..
 read request - map command -- handle command.
 command handler will prepare response and write response.
 Until logout or timeout destroys the session.

 Enter the statemachine from session connection control..
 check ip address

 at each read set timer, so .acdeglstu
 */

